//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <serial.h>
#include <nandflash.h>

#define KERNEL_RAM_START 0xa0020000
#define KERNELLEN        0x500000
#define FLASHOFF         0x40000
#define RAM_START        0xa0000000
#define RAM_SIZE         0x10000000
#define IMAGE_BASE       0xc0020000

typedef struct BootInfo {
    unsigned int LoadAddress;
    unsigned int ImageBase;
    unsigned int ImageSize;
    unsigned int MemorySize;
    unsigned int WriteMagic;
    unsigned int Reserved1;
    unsigned int Reserved2;
    unsigned int Reserved3;
} BootInfo;

typedef struct PackageInfo
{
    unsigned int    uReserved;
    void            *modules;
    unsigned int    uNumberOfModules;
    unsigned int    uPackageSize;
    char            *szCommandLine;
} PackageInfo;

void BootKernel(int arg0, int arg1)
{
    BootInfo *pLoadInfo = (BootInfo *)RAM_START;
    int *pEntry;
    void (*theKernel)(int zero, int arch);
    // find kernel entry
    pEntry = (int*)(KERNEL_RAM_START + 0x3c);

    if ((*(int *)(*pEntry + KERNEL_RAM_START)) != 0x00004550) {
        SerialOutputString("Kernel is not PE format!\n");
        return;
    }
    pEntry = (int*)(KERNEL_RAM_START + (*pEntry) + 0x28);
    theKernel = (void*)((*pEntry) + KERNEL_RAM_START);

    // set kernel arguments
    if (!arg1) {
        arg1 = (int)pLoadInfo;
        pLoadInfo->WriteMagic  = 0xabadbeef;
        pLoadInfo->MemorySize  = RAM_SIZE;
        pLoadInfo->LoadAddress = KERNEL_RAM_START;
        pLoadInfo->ImageBase   = IMAGE_BASE;
        pEntry                 = (int*)(KERNEL_RAM_START + 0x8);
        pLoadInfo->ImageSize   = ((PackageInfo *)(*pEntry + KERNEL_RAM_START))\
            ->uPackageSize;
    }

    SerialOutputString("jump to kernel\n");
    theKernel(arg0,arg1);

    SerialOutputString("Hey, the kernel returned! This should not happen.\r");
}

// read block from uart to memory
void ReadBlock(unsigned char *buffer, long size)
{
    while (size-- > 0)
        *buffer++ = SerialInputByte();
}

void ArmSys()
{
    unsigned long packsize,flashoffset;
    unsigned long buffer = KERNEL_RAM_START;
    long IsWriteFlash;

    SerialOutputRawByte('0');
    SerialOutputRawByte('1');
    SerialOutputRawByte('2');
    SerialOutputRawByte('3');

    // Send query signal,ask for file length
    SerialOutputRawByte('?');

    //  Get file length nowait
    flashoffset = SerialInputDWordWithTimeout();
    packsize = SerialInputDWordWithTimeout();
    IsWriteFlash = packsize & 0x80000000;
    packsize = packsize & ~0x80000000;

    if (packsize) {    // get image from armsys
        // end ready signal
        SerialOutputRawByte('<');

        // receive the file data
        ReadBlock((char*)buffer, packsize);

        SerialOutputRawByte('>');

        // jump to run program if u don't need write flash
        if (IsWriteFlash) {
            SerialOutputString("Write Kernel to Nandflash\n");
            if (cmd_nand_write(buffer, FLASHOFF, packsize) < packsize)
                SerialOutputString("Write Kernel Failed!\n");
            else
                SerialOutputString("Write Kernel Succeeded!\n");
        }

        // image is already in the dram
        BootKernel(0, 0);
    }
}

int c_main()
{
    // initialize useful hardware driver
    if (serial_init(baud_115200)) goto ENDLESS;
    if (init_monahans_nand_driver()) goto ENDLESS;

    // try to get kernel through armsys
    ArmSys();

    // load kernel from Nandflash
    cmd_nand_read(KERNEL_RAM_START, FLASHOFF, KERNELLEN);

    // run into kernel
    BootKernel(0, 0);

ENDLESS:
    SerialOutputString("Bootloader Failed!\n");
    while (1) {
    }

    return 0;
}
